package emulator.hardware.bus;

import emulator.hardware.HwWord;
import emulator.hardware.debug.BusWatchException;
import emulator.hardware.memory.UnmappedMemoryException;

public class VicBus implements Bus {
	public static final int EMPTY_BLOCK = 0;
	public static final int ROM_BLOCK = 1;
	public static final int RAM_BLOCK = 2;
	
	private Bus memory;
	private Bus vic;
	private Bus via1;
	private Bus via2;
	private Bus unconnected;
	private Bus io2;
	private Bus io3;
	
	private int block3k = EMPTY_BLOCK;
	private int block0 = EMPTY_BLOCK;
	private int block1 = EMPTY_BLOCK;
	private int block2 = EMPTY_BLOCK;
	private int block3 = EMPTY_BLOCK;
	
	public VicBus(Bus memory, Bus vic, Bus via1, Bus via2)
	{
		this.memory = memory;
		this.vic = vic;
		this.via1 = via1;
		this.via2 = via2;
		unconnected = new NullBus();
		io2 = unconnected;
		io3 = unconnected;
	}
	
	public void setIO2(Bus io2)
	{
		this.io2 = io2;
	}

	public void setIO3(Bus io3)
	{
		this.io3 = io3;
	}

	@Override
	synchronized public int read(int address)
			throws BusWatchException, UnmappedMemoryException {
		if (isReadableAddress(address))
		{
			HwWord local_address = new HwWord();
			try { 
				return getBus(address, local_address).read((int) local_address.getNumber());
			} catch (Exception e) {
				throw new UnmappedMemoryException(new HwWord(address));
			}
		}
		return 0xff;
	}

	@Override
	synchronized public void write(int address, int data)
			throws BusWatchException, UnmappedMemoryException {
		if (isWritableAddress(address))
		{
			HwWord local_address = new HwWord();
			try { 
				getBus(address, local_address).write((int) local_address.getNumber(), data);
			} catch (BusWatchException e) {
				throw e;
			} catch (Exception e) {
				throw new UnmappedMemoryException(new HwWord(address));
			}
		}
	}
	
	private boolean isReadableAddress(int address_word) {
		boolean is_readable = false;
		long address = address_word;
		if (address < 0x8000)
		{	// 0000-7FFF
			if (address < 0x4000)
			{	// 0000-3FFF
				if (address < 0x2000)
				{	// 0000-1FFF
					if (0x0400 <= address && address < 0x1000)
						is_readable = (block3k != EMPTY_BLOCK);
					else
						is_readable = true;
				}
				else
					is_readable = (block0 != EMPTY_BLOCK);
			}
			else
			{	// 4000-7FFF
				if (address < 0x6000)
					is_readable = (block1 != EMPTY_BLOCK);
				else
					is_readable = (block2 != EMPTY_BLOCK);
			}
		}
		else
		{	// 8000-FFFF
			if (address < 0xC000)
			{	// 8000-BFFF
				if (address < 0xA000)
					is_readable = true;
				else
					is_readable = (block3 != EMPTY_BLOCK);
			}
			else // C000-FFFF ROM
				is_readable = true;
		}
		return is_readable;
	}

	private boolean isWritableAddress(int address_word) {
		boolean is_writable = false;
		long address = address_word;
		if (address < 0x8000)
		{	// 0000-7FFF
			if (address < 0x4000)
			{	// 0000-3FFF
				if (address < 0x2000)
				{	// 0000-1FFF
					if (0x0400 <= address && address < 0x1000)
						is_writable = (block3k == RAM_BLOCK);
					else
						is_writable = true;
				}
				else
					is_writable = (block0 == RAM_BLOCK);
			}
			else
			{	// 4000-7FFF
				if (address < 0x6000)
					is_writable = (block1 == RAM_BLOCK);
				else
					is_writable = (block2 == RAM_BLOCK);
			}
		}
		else
		{	// 8000-FFFF
			if (address < 0xC000)
			{	// 8000-BFFF
				if (address < 0xA000)
					is_writable = (address >= 0x9000);
				else
					is_writable = (block3 == RAM_BLOCK);
			}
			// else C000-FFFF ROM
		}
		return is_writable;
	}

	private Bus getBus(int n_address, HwWord local_address) {
		Bus bus = unconnected;
		local_address.setNumber(n_address);
		if (n_address >= 0x9000 && n_address < 0x9400)
		{ // I/O 00
			if (n_address <= 0x900f)
			{
				local_address.setNumber(n_address&0x0f);
				bus = vic;
			}
			else // if (0x9100 <= n_address)
			{
				local_address.setNumber(n_address&0x0f);
				if ((n_address & 0x10) != 0)
					bus = via1;
				else if ((n_address & 0x20) != 0)
					bus = via2;
			}
		}
		else if (n_address >= 0x9800 && n_address < 0x9C00)
		{
			bus = io2;
		}
		else if (n_address >= 0x9C00 && n_address < 0xA000)
		{
			bus = io3;
		}
		else
		{ // Memory (RAM&ROM)
			bus = memory;
		}
		return bus;
	}

	/**
	 * @return the block3k
	 */
	public int getBlock3k() {
		return block3k;
	}

	/**
	 * @param block3k the block3k to set
	 */
	public void setBlock3k(int block3k) {
		this.block3k = block3k;
	}

	/**
	 * @return the block0
	 */
	public int getBlock0() {
		return block0;
	}

	/**
	 * @param block0 the block0 to set
	 */
	public void setBlock0(int block0) {
		this.block0 = block0;
	}

	/**
	 * @return the block1
	 */
	public int getBlock1() {
		return block1;
	}

	/**
	 * @param block1 the block1 to set
	 */
	public void setBlock1(int block1) {
		this.block1 = block1;
	}

	/**
	 * @return the block2
	 */
	public int getBlock2() {
		return block2;
	}

	/**
	 * @param block2 the block2 to set
	 */
	public void setBlock2(int block2) {
		this.block2 = block2;
	}

	/**
	 * @return the block3
	 */
	public int getBlock3() {
		return block3;
	}

	/**
	 * @param block3 the block3 to set
	 */
	public void setBlock3(int block3) {
		this.block3 = block3;
	}
}
